﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections.ObjectModel;
using System.IO;

namespace AssemblyLoader
{
    [Serializable]
    public class AssemblyLoader
    {

        public AssemblyLoader()
        {

        }
        /// <summary>
        /// Loads the assemblies found in the paths passed and gets all 
        /// the types from those assemblies.
        /// </summary>
        /// <param name="paths"></param>
        public static ObservableCollection<TypeInfo> LoadAssemblies(string[] paths)
        {
            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve +=
                ReflectionOnlyAssemblyResolve;

            string domain_name = AppDomain.CurrentDomain.FriendlyName;

            Assembly curr_assembly;
            Type[] types_found_in_assembly;
            ObservableCollection<TypeInfo> _types_available = new ObservableCollection<TypeInfo>();
            string assembly_filename, new_path;


            string temp_folder_for_assemblies = Path.Combine(Path.GetDirectoryName(
               Assembly.GetAssembly(typeof(AssemblyLoader)).CodeBase), "AssemblyFilesLoaded");
            temp_folder_for_assemblies = new Uri(temp_folder_for_assemblies).LocalPath;
            if (!(Directory.Exists(temp_folder_for_assemblies)))
            {
                Directory.CreateDirectory(temp_folder_for_assemblies);
            }
            foreach (string path in paths)
            {
                assembly_filename = Path.GetFileName(path);
                //new_path = path;

                new_path = Path.Combine(temp_folder_for_assemblies, assembly_filename);
                File.Copy(path, new_path, true);
                if (isDotNetAssembly(new_path))
                {
                    curr_assembly = Assembly.ReflectionOnlyLoadFrom(new_path);
                    try
                    {
                        types_found_in_assembly = curr_assembly.GetTypes();
                        foreach (var type in types_found_in_assembly)
                        {
                            _types_available.Add(new TypeInfo(type));
                        }
                    }
                    catch (ReflectionTypeLoadException e)
                    {
                    //    throw e;
                        foreach (var load_exception in e.LoaderExceptions)
                        {
                            //TODO: convert into custom exception
                            System.Console.Out.WriteLine(load_exception.Message);
                        }
                    }
                }
            }
            _types_available.OrderBy(t => t.Name);
            return _types_available;
        }

        public static Assembly ReflectionOnlyAssemblyResolve(object sender,
            ResolveEventArgs args)
        {
            AssemblyName name = new AssemblyName(args.Name);
            String asmToCheck = name.Name + ".dll";
            if (File.Exists(asmToCheck))
            {
                return Assembly.ReflectionOnlyLoadFrom(name.Name + ".dll");
            }
            else
            {
                return Assembly.ReflectionOnlyLoad(args.Name);
            }
        }

        //The following method was taKEN FROM http://www.codeproject.com/KB/cs/AutoDiagrammer.aspx
        /// <summary>
        /// Returns true if the file specified is a real CLR type, 
        /// otherwise false is returned.
        /// False is also returned in the case of an exception being caught
        /// </summary>
        /// <param name="file">A string representing the file to check for 
        /// CLR validity</param>
        /// <returns>True if the file specified is a real CLR type, 
        /// otherwise false is returned.
        /// False is also returned in the case of an exception being 
        /// caught</returns>
        public static bool isDotNetAssembly(String file)
        {
            uint peHeader;
            uint peHeaderSignature;
            ushort machine;
            ushort sections;
            uint timestamp;
            uint pSymbolTable;
            uint noOfSymbol;
            ushort optionalHeaderSize;
            ushort characteristics;
            ushort dataDictionaryStart;
            uint[] dataDictionaryRVA = new uint[16];
            uint[] dataDictionarySize = new uint[16];

            //get the input stream
            Stream fs = new FileStream(@file, FileMode.Open, FileAccess.Read);

            try
            {

                BinaryReader reader = new BinaryReader(fs);
                //PE Header starts @ 0x3C (60). Its a 4 byte header.
                fs.Position = 0x3C;
                peHeader = reader.ReadUInt32();
                //Moving to PE Header start location...
                fs.Position = peHeader;
                peHeaderSignature = reader.ReadUInt32();
                //We can also show all these value, but we will be
                //limiting to the CLI header test.
                machine = reader.ReadUInt16();
                sections = reader.ReadUInt16();
                timestamp = reader.ReadUInt32();
                pSymbolTable = reader.ReadUInt32();
                noOfSymbol = reader.ReadUInt32();
                optionalHeaderSize = reader.ReadUInt16();
                characteristics = reader.ReadUInt16();

                /*
                Now we are at the end of the PE Header and from here, the
                PE Optional Headers starts...
                To go directly to the datadictionary, we'll increase the
                stream's current position to with 96 (0x60). 96 because,
                28 for Standard fields
                68 for NT-specific fields
                From here DataDictionary starts...and its of total 128 bytes. 
            DataDictionay has 16 directories in total,
                doing simple maths 128/16 = 8.
                So each directory is of 8 bytes.

                In this 8 bytes, 4 bytes is of RVA and 4 bytes of Size.
                btw, the 15th directory consist of CLR header! if its 0, 
            it is not a CLR file :)
                */

                dataDictionaryStart = Convert.ToUInt16
                        (Convert.ToUInt16(fs.Position) + 0x60);
                fs.Position = dataDictionaryStart;
                for (int i = 0; i < 15; i++)
                {
                    dataDictionaryRVA[i] = reader.ReadUInt32();
                    dataDictionarySize[i] = reader.ReadUInt32();
                }
                if (dataDictionaryRVA[14] == 0)
                {
                    fs.Close();
                    return false;
                }
                else
                {
                    fs.Close();
                    return true;
                }
            }
            catch (Exception ex)
            {
                return false;
            }
            finally
            {
                fs.Close();
            }
        }
    }
}
